/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2005-2009 Philippe Durand <draekz@gmail.com>
 * Copyright (C) 2007 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Security.Cryptography;

using Galaxium.Core;
using Anculus.Core;

namespace Galaxium.Protocol.Irc
{
	public sealed class IrcSession : AbstractSession
	{
		public event EventHandler<UserModeEventArgs> UserModeChanged;
		public event EventHandler<EventArgs> ChannelListReceived;
		public event EventHandler<EventArgs> MOTDReceived;
		
		private IrcConnection _connection;
		private bool _disposed;
		private bool _quitting;
		
		private GroupCollection _groups;
		
		private ContactCollection _entities; // For contact list
		private ContactCollection _contacts; // For temp contacts
		private ContactCollection _channels; // For temp channels
		
		private IrcGroup _channelsGroup;
		private IrcGroup _contactsGroup;
		
		private List<ChannelListEntry> _channelList;
		
		private string _MOTD = String.Empty;
		private RemoteServerSettings _remoteServerSettings;
		
		public IrcConnection Connection
		{
			get { return this._connection; }
		}
		
		public override ContactCollection ContactCollection
		{
			get { return this._entities; }
		}
		
		public override GroupCollection GroupCollection
		{
			get { return this._groups; }
		}
		
		public List<ChannelListEntry> ChannelList
		{
			get { return _channelList; }
		}
		
		public RemoteServerSettings RemoteServerSettings
		{
			get { return _remoteServerSettings; }
		}
		
		public string MOTD
		{
			get { return _MOTD; }
			internal set { _MOTD = value; }
		}
		
		public bool Quitting
		{
			get { return _quitting; }
		}
		
		public IrcSession (IrcAccount account) : base (account)
		{
			_conversations = new IrcConversationManager ();

			_groups = new GroupCollection ();
			
			_contacts = new ContactCollection ();
			_channels = new ContactCollection ();
			_entities = new ContactCollection ();
			
			_channelsGroup = new IrcChannelsGroup (this);
			_contactsGroup = new IrcContactsGroup (this);
			
			_groups.Add (_channelsGroup);
			_groups.Add (_contactsGroup);
			
			_channelList = new List<ChannelListEntry> ();
			
			account.ViewByGroup = true;
			account.ShowEmptyGroups = true;
			account.ShowOfflineContacts = true;
			account.UseDefaultListView = false;
			
			_remoteServerSettings = new RemoteServerSettings ();

			_connection = new IrcConnection (this, account.ConnectionInfo);
			_connection.Closed += HandleClosed;
			
			// Read in the stored contacts and channels for the list.
			
			foreach (IrcStoredChannel channel in account.Channels)
			{
				IrcChannel chan = new IrcChannel (this, channel.Name, channel.Key, channel.AutoJoin, channel.Persistent);
				chan.StoredChannel = channel;
				
				_entities.Add (chan);
				_channelsGroup.Add (chan);
				
				EmitContactAdded (new ContactListEventArgs (chan, _channelsGroup));
			}
			
			foreach(IrcStoredContact contact in account.Contacts)
			{
				IrcContact cont = new IrcContact (this, contact.Ident, contact.Name);
				cont.Presence = IrcPresence.Online;
				cont.StoredContact = contact;
				
				_entities.Add (cont);
				_contactsGroup.Add (cont);
				
				EmitContactAdded (new ContactListEventArgs (cont, _contactsGroup));
			}
		}

		void HandleClosed(object sender, ConnectionEventArgs e)
		{
			OnDisconnected (new SessionEventArgs (this));
		}

		protected override void Dispose (bool disposing)
		{
			if (Connection.IsConnected)
				Disconnect ();
			
			if (!_disposed)
				if (disposing && _connection != null)
					_connection.Dispose ();
			
			_disposed = true;
		}
		
		public override void Connect ()
		{
			_connection.Connect ();
			OnConnected (new SessionEventArgs (this));
		}

		public override void Disconnect ()
		{
			_quitting = true;
			
			_connection.Disconnect ();
			
			OnDisconnected (new SessionEventArgs (this));
		}
		
		public void Quit (string reason)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Quit);
			if (!String.IsNullOrEmpty (reason))
			{
				sb.Append (' ');
				sb.Append (reason);
			}
			
			_connection.Send (sb);
		}
		
		public void GetMOTD (string server)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.MOTD);
			if (!String.IsNullOrEmpty (server))
			{
				sb.Append (' ');
				sb.Append (server);
			}
			
			_connection.Send (sb);
		}
		
		public void GetNames (string channels, string server)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Names);
			if (!String.IsNullOrEmpty (channels))
			{
				sb.Append (' ');
				sb.Append (channels);
				if (!String.IsNullOrEmpty (server))
				{
					sb.Append (' ');
					sb.Append (server);
				}
			}
			
			_connection.Send (sb);
		}
		
		public void GetLUsers (string mask, string server)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.LUsers);
			if (!String.IsNullOrEmpty (mask))
			{
				sb.Append (' ');
				sb.Append (mask);
				if (!String.IsNullOrEmpty (server))
				{
					sb.Append (' ');
					sb.Append (server);
				}
			}
			
			_connection.Send (sb);
		}
		
		public void AuthOperator (string username, string password)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Operator);
			sb.Append (' ');
			sb.Append (username);
			sb.Append (' ');
			sb.Append (password);
			
			_connection.Send (sb);
		}
		
		public void GetTime (string server)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Time);
			if (!String.IsNullOrEmpty (server))
			{
				sb.Append (' ');
				sb.Append (server);
			}
			
			_connection.Send (sb);
		}
		
		public void GetStats (string query, string server)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Stats);
			if (!String.IsNullOrEmpty (query))
			{
				sb.Append (' ');
				sb.Append (query);
				if (!String.IsNullOrEmpty (server))
				{
					sb.Append (' ');
					sb.Append (server);
				}
			}
			
			_connection.Send (sb);
		}
		
		public void GetServQuery (string server, string query)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.ServQuery);
			sb.Append (' ');
			sb.Append (server);
			sb.Append (' ');
			sb.Append (query);
			
			_connection.Send (sb);
		}
		
		public void GetServList (string mask, string type)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.ServList);
			if (!String.IsNullOrEmpty (mask))
			{
				sb.Append (' ');
				sb.Append (mask);
				if (!String.IsNullOrEmpty (type))
				{
					sb.Append (' ');
					sb.Append (type);
				}
			}
			
			_connection.Send (sb);
		}
		
		public void GetAdmin (string server)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Admin);
			if (!String.IsNullOrEmpty (server))
			{
				sb.Append (' ');
				sb.Append (server);
			}
			
			_connection.Send (sb);
		}
		
		public void GetInfo (string server)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Info);
			if (!String.IsNullOrEmpty (server))
			{
				sb.Append (' ');
				sb.Append (server);
			}
			
			_connection.Send (sb);
		}
		
		public void SetAwayPresence (IPresence presence)
		{
			if (presence == IrcPresence.Online)
				ChangeAway (null);
			else
				ChangeAway (Account.DisplayMessage);
		}
		
		public void SendWallOps (string message)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Wallops);
			if (!String.IsNullOrEmpty (message))
			{
				sb.Append (' ');
				sb.Append (message);
			}
			
			_connection.Send (sb);
		}
		
		public void SendMessage (string channel, string message)
		{
			IrcProtocolHelper.ThrowIfInvalidChannelName (_remoteServerSettings, channel);
			
			SendMessage (IrcConstants.Commands.PrivateMessage, channel, message);
		}
		
		public void SendPrivateMessage (string user, string message)
		{
			IrcProtocolHelper.ThrowIfInvalidNickname (_remoteServerSettings, user);
			
			SendMessage (IrcConstants.Commands.PrivateMessage, user, message);
		}
		
		public void SendNotice (string channel, string message)
		{
			IrcProtocolHelper.ThrowIfInvalidChannelName (_remoteServerSettings, channel);
			
			SendMessage (IrcConstants.Commands.Notice, channel, message);
		}
		
		public void SendPrivateNotice (string user, string message)
		{
			IrcProtocolHelper.ThrowIfInvalidNickname (_remoteServerSettings, user);
			
			SendMessage (IrcConstants.Commands.Notice, user, message);
		}
		
		public override IFileTransfer SendFile (IContact contact, string filename)
		{
			ThrowUtility.ThrowIfNull ("contact", contact);
			ThrowUtility.ThrowIfEmpty ("filename", filename);
			
			IrcFileTransfer fileTransfer = new IrcFileTransfer (this, contact, filename);
			
			AllFileTransfers.Add (fileTransfer);
			EmitTransferInvitationSent (new FileTransferEventArgs (fileTransfer));
			
			return fileTransfer;
		}
		
		internal void SendMessage (string command, string target, string message)
		{
			SendMessage (command, target, message, true);
		}
		
		internal void SendMessage (string command, string target, string message, bool checkLength)
		{
			if (checkLength) {
				int len = command.Length + target.Length + 2;
				if ((len + message.Length) > IrcConstants.MaxCommandSize) {
					string[] chunks = IrcProtocolHelper.SplitMessage (message, IrcConstants.MaxCommandSize - len);
					foreach (string chunk in chunks)
						SendMessage (command, target, chunk, false);
					return;
				} 
			}

			StringBuilder sb = new StringBuilder ();
			sb.Append (command);
			sb.Append (' ');
			sb.Append (target);
			sb.Append (" :");
			sb.Append (message);
			
			_connection.Send (sb);
		}
		
		public void ChangeNickname (string nickname)
		{
			IrcProtocolHelper.ThrowIfInvalidNickname (_remoteServerSettings, nickname);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Nickname);
			sb.Append (' ');
			sb.Append (nickname);
			
			_connection.Send (sb);
		}
		
		public void SendQuit (string comment)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Quit);
			if (comment != null) {
				sb.Append (' ');
				sb.Append (comment);
			}
			_connection.Send (sb);
		}
		
		public void ListChannels (string channels, string server)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.List);
			if (!String.IsNullOrEmpty(channels))
			{
				sb.Append (' ');
				sb.Append (channels);
				if (!String.IsNullOrEmpty(server))
				{
					sb.Append (' ');
					sb.Append (server);
				}
			}
			
			_connection.Send (sb);
		}
		
		public void RemoveChannel (IrcChannel channel)
		{
			IrcConversation conv = Conversations.GetConversation (channel) as IrcConversation;
			if (conv != null && conv.IsJoined)
				PartChannels (channel.DisplayName);
			
			_channelsGroup.Remove (channel); // Remove it from the group.
			_entities.Remove (channel); // Remove it from the collection of entities
			(Account as IrcAccount).RemoveChannel (channel); // Remove from save
			
			EmitContactRemoved (new ContactListEventArgs (channel, _channelsGroup));
		}
		
		public void AddContact (string name, string ident)
		{
			// This command should be called when adding, not talking to a contact thats
			// not on the list.
			
			IrcContact contact = ContactCollection.GetContact (name) as IrcContact;
			
			if (contact == null)
			{
				contact = new IrcContact (this, ident, name);
				
				// Add to the session's entity collection
				_entities.Add (contact);
				
				// Add to the channels group
				_contactsGroup.Add (contact);
				
				// Remove from the stored contacts collection
				IrcStoredContact storedContact = new IrcStoredContact (contact.DisplayName, contact.Ident);
				(Account as IrcAccount).Contacts.Add (storedContact);
				contact.StoredContact = storedContact;
				
				EmitContactAdded (new ContactListEventArgs (contact, _contactsGroup));
			}
		}
		
		public void RemoveContact (string name)
		{
			IrcContact contact = ContactCollection.GetContactByName (name) as IrcContact;
			
			if (contact != null)
			{
				// Remove from the session's collection
				_entities.Remove (contact);
				
				// Remove from the channels group
				_contactsGroup.Remove (contact);
				
				// Remove from the stored contacts collection
				(Account as IrcAccount).RemoveContact (contact);
				
				EmitContactRemoved (new ContactListEventArgs (contact, _contactsGroup));
			}
		}
		
		public void AddChannel (string channel, string key, bool autoJoin, bool persistent)
		{
			// This command should only be used to add a channel to the list, not temporarily
			// join one.
			
			IrcProtocolHelper.ThrowIfInvalidChannelName (_remoteServerSettings, channel);
			
			//the state of the channel is changed to online once the NAMES list of a channel is received
			IrcChannel chan = ObtainChannel (channel, false);
			
			if (chan == null)
			{
				chan = new IrcChannel (this, channel, key, autoJoin, persistent);	
				IrcStoredChannel stored = new IrcStoredChannel (channel, key, autoJoin, persistent);
				chan.StoredChannel = stored;
				(Account as IrcAccount).Channels.Add (stored);
				
				_entities.Add (chan);
				_channelsGroup.Add (chan);
				
				EmitContactAdded (new ContactListEventArgs (chan, _channelsGroup));
			}
			
			if (autoJoin)
			{
				ObtainConversation (chan, true);
				
				StringBuilder sb = new StringBuilder ();
				sb.Append (IrcConstants.Commands.Join);
				sb.Append (' ');
				sb.Append (channel);
				if (!String.IsNullOrEmpty (key)) {
					sb.Append (' ');
					sb.Append (key);
				}
				
				_connection.Send (sb);
			}
		}
		
		public void JoinChannel (string channel, string key, bool autoJoin, bool persistent)
		{
			IrcProtocolHelper.ThrowIfInvalidChannelName (_remoteServerSettings, channel);
			
			//the state of the channel is changed to online once the NAMES list of a channel is received
			IrcChannel chan = ObtainChannel (channel, true);
			
			if (chan == null)
			{
				chan = new IrcChannel (this, channel, key, autoJoin, persistent);	
				IrcStoredChannel stored = new IrcStoredChannel (channel, key, autoJoin, persistent);
				chan.StoredChannel = stored;
				(Account as IrcAccount).Channels.Add (stored);
				
				_entities.Add (chan);
				_channelsGroup.Add (chan);
				
				EmitContactAdded (new ContactListEventArgs (chan, _channelsGroup));
			}
			
			//ObtainConversation (chan, true);
			
			//Log.Debug ("Calling join to channel.");
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Join);
			sb.Append (' ');
			sb.Append (channel);
			if (!String.IsNullOrEmpty (key)) {
				sb.Append (' ');
				sb.Append (key);
			}
			
			_connection.Send (sb);
		}
		
		public void JoinChannel (IrcChannel channel)
		{
			if (channel == null)
				throw new ArgumentNullException ("channel");

			JoinChannel (channel.UniqueIdentifier, channel.Key, channel.AutoJoin, channel.Persistent);
		}
		
		public void PartChannels (params string[] channels)
		{
			ThrowUtility.ThrowIfArrayIsEmpty ("channels", channels);

			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Part);
			sb.Append (' ');
			
			bool first = true;
			foreach (string channelName in channels) {
				if (first)
					first = false;
				else
					sb.Append (',');
				sb.Append (channelName);
				
				//change the state of the channel to offline
				IrcChannel chan = ObtainChannel (channelName, true);
				if (chan != null)
					chan.Presence = IrcPresence.Offline;
			}
			
			_connection.Send (sb);
		}
		
		public void ChangeChannelMode (string channel, bool grant, ChannelModeFlags flags)
		{
			IrcProtocolHelper.ThrowIfInvalidChannelName (_remoteServerSettings, channel);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Mode);
			sb.Append (' ');
			sb.Append (channel);
			sb.Append (' ');
			sb.Append (grant ? '+' : '-');

			if ((flags & ChannelModeFlags.AnonymousChannel) == ChannelModeFlags.AnonymousChannel) {
				sb.Append ('a');
			} else if ((flags & ChannelModeFlags.InvitationMask) == ChannelModeFlags.InvitationMask) {
				sb.Append ('I');
			} else if ((flags & ChannelModeFlags.InviteOnlyChannel) == ChannelModeFlags.InviteOnlyChannel) {
				sb.Append ('i');
			} else if ((flags & ChannelModeFlags.Moderated) == ChannelModeFlags.Moderated) {
				sb.Append ('m');
			} else if ((flags & ChannelModeFlags.NoExternalMessages) == ChannelModeFlags.NoExternalMessages) {
				sb.Append ('n');
			} else if ((flags & ChannelModeFlags.OnlyOperChangeTopic) == ChannelModeFlags.OnlyOperChangeTopic) {
				sb.Append ('t');
			} else if ((flags & ChannelModeFlags.PrivateChannel) == ChannelModeFlags.PrivateChannel) {
				sb.Append ('p');
			} else if ((flags & ChannelModeFlags.QuietChannel) == ChannelModeFlags.QuietChannel) {
				sb.Append ('q');
			} else if ((flags & ChannelModeFlags.SecretChannel) == ChannelModeFlags.SecretChannel) {
				sb.Append ('s');
			} else if ((flags & ChannelModeFlags.ServerReopChannel) == ChannelModeFlags.ServerReopChannel) {
				sb.Append ('r');
			} else {
				Log.Warn ("Invalid ChannelModeFlags: {0}", flags);
				return;
			}
			
			_connection.Send (sb);
		}
		
		public void GetMode (string identifier)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Mode);
			sb.Append (' ');
			sb.Append (identifier);
			
			_connection.Send (sb);
			
		}
		
		public void ChangeMode (string identifier, string flags, string args)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Mode);
			sb.Append (' ');
			sb.Append (identifier);
			sb.Append (' ');
			sb.Append (flags);
			if (!String.IsNullOrEmpty (args))
			{
				sb.Append (' ');
				sb.Append (args);
			}
			
			_connection.Send (sb);
		}
		
		public void ChangeUserChannelMode (string channel, string nickname, bool grant, ChannelModeFlags flags)
		{
			IrcProtocolHelper.ThrowIfInvalidChannelName (_remoteServerSettings, channel);
			IrcProtocolHelper.ThrowIfInvalidNickname (_remoteServerSettings, nickname);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Mode);
			sb.Append (' ');
			sb.Append (channel);
			sb.Append (' ');
			sb.Append (grant ? '+' : '-');
			
			if ((flags & ChannelModeFlags.ChannelCreator) == ChannelModeFlags.ChannelCreator) {
				sb.Append ('O');
			} else if ((flags & ChannelModeFlags.Operator) == ChannelModeFlags.Operator) {
				sb.Append ('o');
			} else if ((flags & ChannelModeFlags.HalfOperator) == ChannelModeFlags.HalfOperator) {
				sb.Append ('h');
			} else if ((flags & ChannelModeFlags.Voice) == ChannelModeFlags.Voice) {
				sb.Append ('v');
			} else {
				Log.Warn ("Invalid ChannelModeFlags: {0}", flags);
				return;
			}
			
			sb.Append (' ');
			sb.Append (nickname);
			
			_connection.Send (sb);
		}
		
		public void BanUserFromChannel (string channel, IrcContact contact)
		{
			string mask = null;
			if (contact.Ident != null) {
				mask = "*!" + contact.Ident;
			} else {
				mask = contact.DisplayName + "!*@*";
			}
			
			ChangeBanUserFromChannel (channel, mask, true);
		}
		
		public void BanUserFromChannel (string channel, string mask)
		{
			ChangeBanUserFromChannel (channel, mask, true);
		}
		
		public void UnbanUserFromChannel (string channel, string mask)
		{
			ChangeBanUserFromChannel (channel, mask, false);
		}
		
		internal void ChangeBanUserFromChannel (string channel, string mask, bool grant)
		{
			IrcProtocolHelper.ThrowIfInvalidChannelName (_remoteServerSettings, channel);
			ThrowUtility.ThrowIfEmpty ("mask", mask);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Mode);
			sb.Append (' ');
			sb.Append (channel);
			if (grant)
				sb.Append (" +b ");
			else
				sb.Append (" -b ");
			sb.Append (mask);
			
			_connection.Send (sb);
		}
		
		public void ChangeChannelLimit (string channel, int limit)
		{
			IrcProtocolHelper.ThrowIfInvalidChannelName (_remoteServerSettings, channel);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Mode);
			sb.Append (' ');
			sb.Append (channel);
			if (limit < 0) {
				sb.Append ("-l");
			} else {
				sb.Append ("+l ");
				sb.Append (limit);
			}
			_connection.Send (sb);
		}
		
		public void SetChannelKey (string channel, string key)
		{
			ChangeChannelKey (channel, key, true);
		}
		
		public void RemoveChannelKey (string channel, string key)
		{
			ChangeChannelKey (channel, key, false);
		}
		
		public void ChangeChannelKey (string channel, string oldKey, string newKey)
		{
			ChangeChannelKey (channel, oldKey, false);
			ChangeChannelKey (channel, newKey, true);
		}
		
		internal void ChangeChannelKey (string channel, string key, bool grant)
		{
			IrcProtocolHelper.ThrowIfInvalidChannelName (_remoteServerSettings, channel);
			if (String.IsNullOrEmpty (key))
				throw new ArgumentNullException ("key");
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Mode);
			sb.Append (' ');
			sb.Append (channel);
			sb.Append (grant ? '+' : '-');
			sb.Append ("k ");
			sb.Append (key);
			_connection.Send (sb);
		}
		
		public void RemoveChannelLimit (string channel)
		{
			ChangeChannelLimit (channel, -1);
		}
		
		public void GetVersion (string server)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Version);
			sb.Append (' ');
			sb.Append (server);
			
			_connection.Send (sb);
		}
		
		public void GetUsers (string server)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Users);
			sb.Append (' ');
			sb.Append (server);
			
			_connection.Send (sb);
		}
		
		public void GetUserHost (string users)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Userhost);
			sb.Append (' ');
			sb.Append (users);
			
			_connection.Send (sb);
		}
		
		public void ChangeTopic (string channel, string topic)
		{
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Topic);
			sb.Append (' ');
			sb.Append (channel);
			if (topic != null)
			{
				sb.Append (" :");
				sb.Append (topic);
			}
			
			_connection.Send (sb);
		}
		
		public void InviteUser (string channel, string user)
		{
			IrcProtocolHelper.ThrowIfInvalidChannelName (_remoteServerSettings, channel);
			IrcProtocolHelper.ThrowIfInvalidNickname (_remoteServerSettings, user);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Invite);
			sb.Append (' ');
			sb.Append (user);
			sb.Append (' ');
			sb.Append (channel);
			
			_connection.Send (sb);
		}
		
		public void KickUser (string channel, string user)
		{
			KickUser (channel, user, null);
		}
		
		public void KickUser (string channel, string user, string comment)
		{
			IrcProtocolHelper.ThrowIfInvalidChannelName (_remoteServerSettings, channel);
			IrcProtocolHelper.ThrowIfInvalidNickname (_remoteServerSettings, user);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Kick);
			sb.Append (' ');
			sb.Append (channel);
			sb.Append (' ');
			sb.Append (user);
			sb.Append (" :");
			if (comment != null)
				sb.Append (comment);
			
			_connection.Send (sb);
		}
		
		public void KillUser (string user)
		{
			KickUser (user, null);
		}
		
		public void KillUser (string user, string comment)
		{
			IrcProtocolHelper.ThrowIfInvalidNickname (_remoteServerSettings, user);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Kill);
			sb.Append (' ');
			sb.Append (user);
			sb.Append (" :");
			if (comment != null)
				sb.Append (comment);
			
			_connection.Send (sb);
		}
		
		public void WhoUser (string user)
		{
			IrcProtocolHelper.ThrowIfInvalidNickname (_remoteServerSettings, user);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Who);
			sb.Append (' ');
			sb.Append (user);

			_connection.Send (sb);
		}
		
		public void WhoisUser (string user)
		{
			IrcProtocolHelper.ThrowIfInvalidNickname (_remoteServerSettings, user);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.WhoIs);
			sb.Append (' ');
			sb.Append (user);

			_connection.Send (sb);
		}
		
		public void WhowasUser (string user)
		{
			IrcProtocolHelper.ThrowIfInvalidNickname (_remoteServerSettings, user);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.WhoWas);
			sb.Append (' ');
			sb.Append (user);

			_connection.Send (sb);
		}
		
		public void ChangeAway (string away)
		{
			if (away != null)
				(Account as IrcAccount).SetDisplayMessage (away);
			
			StringBuilder sb = new StringBuilder ();
			sb.Append (IrcConstants.Commands.Away);
			if (away != null)
			{
				sb.Append (" :");
				sb.Append (String.IsNullOrEmpty (away) ? " ":away);
			}
			
			_connection.Send (sb);
		}
		
		public IrcContact ObtainContact (string name, string ident)
		{
			// FIXME: We are not using IDENT really...
			
			// Check to see if the contact exists in a channel somewhere.
			ThrowUtility.ThrowIfNull ("name", name);
			
			// Check to see if we have the contact in our list
			IrcContact contact = _entities.GetContactByName(name) as IrcContact;
			
			if (contact == null)
			{
				// Look for the contact in our temporary list.
				contact = _contacts.GetContactByName (name) as IrcContact;
				
				if (contact == null)
				{
					// Lets create a temporary contact where we dont store.
					contact = new IrcContact (this, ident, name);
					_contacts.Add(contact);
				}
			}
			
			return contact;
		}
		
		public IrcConversation ObtainConversation (IContact entity, bool create)
		{
			if (entity == null)
				return null;
			
			IrcConversationManager manager = _conversations as IrcConversationManager;
			IrcConversation conversation = null;
			
			if (entity is IrcChannel)
				conversation = manager.GetChannelConversation (entity as IrcChannel);
			else
			{
				conversation = manager.GetConversation (entity) as IrcConversation;
				(entity as IrcContact).Conversation = conversation;
			}
			
			if (conversation == null && create)
			{
				conversation = manager.CreateConversation (entity) as IrcConversation;
				
				// Should this one always be fired? I'm going to disable and see what happens.
				//EmitConversationInitiated (new ConversationEventArgs (conversation));
				
				if (entity is IrcContact)
					(entity as IrcContact).Conversation = conversation;
			}
			
			if (conversation == null)
				Log.Warn ("Attempted to obtain a conversation for ["+entity.DisplayName+"] but failed!");
			
			return conversation;
		}
		
		public IrcChannel ObtainChannel (string name, bool create)
		{
			ThrowUtility.ThrowIfNull ("name", name);
			
			IrcChannel channel = _entities.GetContactByName (name) as IrcChannel;
			
			if (channel == null && create)
			{
				// Look for the contact in our temporary list.
				channel = _channels.GetContactByName (name) as IrcChannel;
				
				if (channel == null)
				{
					// Lets create a temporary contact where we dont store.
					channel = new IrcChannel(this, name, String.Empty, false, false);
					_channels.Add(channel);
				}
			}
			
			return channel;
		}
		
		/*public IrcContact GetContact (string name)
		{
			if (name == null)
				throw new ArgumentNullException ("name");
			
			foreach (IContact entity in _channels)
			{
				if (entity is IrcContact)
				{
					IrcContact contact = entity as IrcContact;
					if (contact.DisplayName.CompareTo (name) == 0)
						return contact;
				}
			}
			
			return null;
		}
		
		*/
		
		public override void SetPresence (BasePresence presence)
		{
			switch (presence) {
			case BasePresence.Away:
			case BasePresence.Invisible:
			case BasePresence.Offline:
			case BasePresence.Idle:
				SetAwayPresence (IrcPresence.Away);
				break;
			case BasePresence.Online:
			case BasePresence.Unknown:
			default:
				SetAwayPresence (IrcPresence.Online);
				break;
			}
		}

		internal void EmitMOTDReceived (EventArgs args)
		{
			if (MOTDReceived != null)
				MOTDReceived (this, args);
		}
		
		internal void EmitChannelListReceived (EventArgs args)
		{
			if (ChannelListReceived != null)
				ChannelListReceived (this, args);
		}
		
		internal void EmitUserModeChanged (UserModeEventArgs args)
		{
			if (UserModeChanged != null)
				UserModeChanged (this, args);
		}
	}
}